package com.lxit.cms.action.admin.assist.cn.compent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.lxit.cms.action.admin.assist.cn.msg.MsgCommand;
import com.lxit.cms.action.admin.assist.cn.msg.MsgDeliver;
import com.lxit.cms.action.admin.assist.cn.msg.MsgDeliverResp;
import com.lxit.cms.action.admin.assist.cn.msg.MsgHead;
import com.lxit.cms.action.admin.assist.cn.msg.MsgLogin;
import com.lxit.cms.action.admin.assist.cn.msg.MsgLoginResp;
import com.lxit.cms.action.admin.assist.cn.msg.MsgSubmit;
import com.lxit.cms.action.admin.assist.cn.msg.MsgSubmitResp;
import com.lxit.cms.action.admin.assist.cn.util.MsgTools;
import com.lxit.cms.action.admin.assist.cn.util.SysteLog;
import com.lxit.cms.action.admin.assist.cn.util.Utils;
/**
 *在接收,处理消息时,避免线程jing zhing 
 * 接收线程:使用双队列缓冲,又不要死锁
 * cmpp协议连结对象:
 * 在独立的的线程上接收消息块的数据,并调用打包工具类解析
 * @project:蓝杰中国移动Cmpp短信网关项目开发 Jun 17, 2009
 * @author: 蓝杰www.netjava.cn
 */

public class CMPPConntor extends Thread {
	
	private java.net.Socket sc;//网络连结对象
	private int sendCount;//一秒内己发送的计数器
	//从连结上得到的输入输出流
	private java.io.DataInputStream dins;
	private java.io.DataOutputStream dous;
	   //存放己回过应答的Submit消息
	private List<MsgSubmit> submitsResult;
	private List<MsgDeliver> deliversResult;
	   //存放接收到的Submit消息:双缓冲队列
	   private List<MsgSubmit> submitList1=new ArrayList();
	   
	 //等待应答的deliver消息队列
	   private Map<Integer,MsgDeliver> delivers=new HashMap();
	   
	
	   private boolean isRunning=true;//运行标志
	
 /**
  * 创建一个cmpp连结对象
  * @param sc:tcp/ip连结
  * @param submits:保存接收到的Submit消息对象
  * @throws Exception
  */
   public CMPPConntor(java.net.Socket sc,List<MsgSubmit> submitsResult,List<MsgDeliver> deliversResult)throws Exception{
	   try{
	   this.submitsResult=submitsResult;
	   this.deliversResult=deliversResult;
	   this.sc=sc;
	   sc.setReceiveBufferSize(2048);//设定Socket缓冲区大小
//	   sc.setSoTimeout(1000);//超时为1秒  //如果超时到了,会返回什么呢??
	   dins=new java.io.DataInputStream(sc.getInputStream());
	   dous=new java.io.DataOutputStream(sc.getOutputStream());
	   }catch(Exception ef){
		   ef.printStackTrace();
	   }
   }
   
   /**
    * 本连结对象上一秒己发送消息的个数
    * @return
    */
   public int getSendCount(){
		return sendCount;
	}
   
   /**
    * 在此连结上发送DeliverMsg
    * 此方法不能发送状态报告
    * @param msg
    */
   public boolean sendDeliver(String srcMobile,String msg,String desSp){
//	  根据传入的参数,构造一个Deliver消息
	   MsgDeliver md= new MsgDeliver();
	   int mlen=msg.getBytes().length;//消息内容的长度
	    
	   int totalLen=12+8+21+10+1+1+1+32+1+1+1+mlen+20;
	   
	   md.setTotal_Length(totalLen);
	   md.setCommand_Id(MsgCommand.CMPP_DELIVER);
	   md.setSequence_Id(Utils.getSeq());
	   
	   md.setMsg_Id(0);//接收方收到后,在应答包中回送的
       md.setDest_Id(desSp);
       md.setService_Id("test");
       md.setSrc_terminal_Id(srcMobile);
       
       md.setRegistered_Delivery((byte)0);
       md.setMsg_Length((byte)mlen);
       md.setMsg_Content(msg);
       //LinkID:要生成一个20位的唯一字符串
       md.setLinkID("linkID"+Utils.getMMDDHHMMSS()+Utils.getSeq());//模拟生成
	   delivers.put(md.getSequence_Id(), md);//发送前先加入队列
	   byte[] data=MsgTools.packMsg(md);
	   this.sendMsg(data);
	   return true;
   }
   
   
   
    
  /**
   * 在本连结上发送己打包后的消息的字节
   * @param data:要发送消息的字节
   */
   public void sendMsg(byte[] data) {
	   try{
		   Utils.debugData("ISMG发出的原始数据>>>:",data);
		sendCount++;
	   //可以将多条消息打到一个包中
	   dous.write(data);
	   dous.flush();
	   }catch(Exception ef){
		   
	   }
   }
   
  
   
   
  /**
   * 判断这个连结是否登陆成功
   * @param spid:sp企业ID
   * @param pwd:sp密码
   * @return:登陆结果
   */
   public boolean checkLogin(String spid,String pwd){
	   //第一次连上来的SP端,必然是发送一个login消息请求
	   byte[] data=recv();//读取消息的数据
	   //对消息进行解包
	     MsgHead msg=MsgTools.parseMsg(data);
	   //解包为一个LoginMsg对象
		   MsgLogin ml=(MsgLogin)msg;
	    //对spid,pwd,MD5串进行验证?
	     //简单验证
		  boolean loginResult=spid.equals(ml.getSource_Addr().trim()); 
		  SysteLog.INFO("服务器收到","请求sp的spId为"+ml.getSource_Addr()+",服务器端配置的为:"+spid);
	   //打包一个登陆应答消息
	   MsgLoginResp mlr=new MsgLoginResp();
	   mlr.setTotal_Length(12+4+16+1);
	   mlr.setCommand_Id(MsgCommand.CMPP_CONNECT_RESP);
	   mlr.setSequence_Id(Utils.getSeq());
	   //md5认证串
	   mlr.setAuthenticatorISMG(Utils.getLoginMd5(spid, pwd));
	   if(loginResult){
	   mlr.setStatus(0);//假设登陆成功
	   mlr.setVersion((byte)14);//版本号
	   //设置各项参数
	   byte[] respData=MsgTools.packMsg(mlr);////打包的应答  ????
	    this.sendMsg(respData);
	    
	   //读SP端来的消息
	   }else{
	   mlr.setStatus(1);//sp登陆失败！
	   mlr.setVersion((byte)14);//版本号
	   //设置各项参数
	   byte[] respData=MsgTools.packMsg(mlr);////打包的应答  ????
	    this.sendMsg(respData);
	    //关闭连结
	    close();
	   }
	   return loginResult;
   }
   
    //关闭连结
   public void close(){
	   try{
	   isRunning=false;
	   this.sc.close();
	   }catch(Exception ef){
		 //  ef.printStackTrace();
	   }
   }
   
   
   /**
    * IMSG循环接收消息的线程
    */
   public void run(){
	 //启动处理接收消息线程
	    processSubmit();
	   while(true){
		   byte[] b=recv();//接收消息,以数据据块的方式读取
		   //在这里,还有可能收到login请求
          if(null==b){
        	  break;
          }
		  //将读到的数据块解包为对应的消息对象
       MsgHead msg=   MsgTools.parseMsg(b);
           SysteLog.INFO("服务器收到的内容:", msg);
     //收到的是SP发来的Submit消息
       if(msg.getCommand_Id()==MsgCommand.CMPP_SUBMIT){
    	   MsgSubmit ms=(MsgSubmit)msg;
    	   if(submitList1.size()>16){
    		//回一个应答state为8的消息   
    	   }else{
    	  //将收到的消息加入到处理队列中
    	   submitList1.add(ms);
    	   }
       }
     //收到的是SP发来的DELIVER应答消息
       if(msg.getCommand_Id()==MsgCommand.CMPP_DELIVER_RESP){
    	 MsgDeliverResp ms=(MsgDeliverResp)msg;
    	 int mid=ms.getSequence_Id();
    	 MsgDeliver md=delivers.get(mid);
    	 if(md!=null){//如果收到了应答
    		 //应答结果保存到Deliver消息中
    		 int state=ms.getResult();
    		 md.setSendResult(state);
    		 deliversResult.add(md);
    		 //从等待应答队列中移除
    		 delivers.remove(mid);
    	   }
         }
       //如果收到其它消息

	   }
   }
	
   /**
    * 处理接收到的Submit消息
    *  流量未超标
    * 1.负责生成msgid,
    * 2.回应答,
    * 3.将本条Submit消息交给其它模块处理
    */
   private void processSubmit(){
	   java.lang.Runnable runner=new java.lang.Runnable(){
		   public void run(){
			   while(true){
				   //处理队列1
		   for(int i=0;i<submitList1.size();i++){
				   MsgSubmit ms=submitList1.get(i);
			   //取出来,创建一个应答消息对象回应答
			   sendSubmitResp(ms);
			   //将发送过应答结果的消息放到回收队列中
			   submitsResult.add(ms);
			   submitList1.remove(i);
		     }
		    sleepThread(10);
	 		 }
		   }
	   };
	   Thread t=new Thread(runner);
	   t.start();
   }
   
   
   //发送一个submit消息对应的应答消息
   private void sendSubmitResp(MsgSubmit ms){
	    
	   MsgSubmitResp rs=new MsgSubmitResp();
	   rs.setTotal_Length(12+8+4);
	   rs.setCommand_Id(MsgCommand.CMPP_SUBMIT_RESP);
	   rs.setSequence_Id(ms.getSequence_Id());
	   //模拟生成消息ＩＤ
	   long mid=java.util.UUID.randomUUID().getLeastSignificantBits();
	   ms.setMsg_Id(mid);
	   rs.setMsg_Id(mid);
	   rs.setResult(0);    //消息正确
	   
	   byte[] data=MsgTools.packMsg(rs);
	   this.sendMsg(data);
	   
   }
   
   
   
   private void sleepThread(int i){
	   try{
		   Thread.sleep(i);
	   }catch(Exception ef){
		   
	   }
   }
 
   
   /**
    * 从输入流上读取消息的原始数据块，注：不含消息头总长四个字节
    */
   private byte[] recv(){
	   try{
	   int len=dins.readInt();
	   //cmpp消息最长是不会招过200字节的
	   //1.恶意消息 10 10000
	   if((len>0)&&(len<500)){
	   byte[] data=new byte[len-4];
	   
	   dins.readFully(data);  //如果超时了呢?
	 //这里的data,才是最原始的数据
	   Utils.debugData("IMSG收到的原始数据<<<:", data);
	   return data;
	   }else{
		   //1.丢出异常
		   //2.要么断开它
	   }
	   }catch(Exception ef){
		   ef.printStackTrace();
	   }
	   return null;
   }
   
  
	
	 
	 
	 
	
}
